#include <dlfcn.h>      // dlopen, dlsym, dlclose
#include <unistd.h>     // getcwd
#include <sys/param.h>  // MAXPATHLEN
#include <ApplicationServices/ApplicationServices.h>
#include "ocl.h"

int m_nWidth  	  = 0;
int m_nHeight 	  = 0;
int m_nStride 	  = 0;
int m_nImageID	  = 1;
int m_nBufferID   = 1;
int m_nColorDepth = 8;
int m_nBoundary   = 255;

void saveImage(CGImageRef imgRef, char* fpath)
{
	char cwdpath[MAXPATHLEN];
	char abspath[MAXPATHLEN];

	getcwd(cwdpath, MAXPATHLEN);
	sprintf(abspath, "%s/%s", cwdpath, fpath);

    CFStringRef strRef = CFStringCreateWithCString(NULL, abspath, kCFStringEncodingMacRoman);
    CFStringRef typRef = CFStringCreateWithCString(NULL, "public.png", kCFStringEncodingMacRoman);
    CFURLRef    urlRef = CFURLCreateWithFileSystemPath(NULL, strRef, 0, false);

    CGImageDestinationRef dstRef = CGImageDestinationCreateWithURL(urlRef, typRef, 1, NULL);

    CGImageDestinationAddImage(dstRef, imgRef, NULL);
    CGImageDestinationFinalize(dstRef);
}
void saveContext(CGContextRef ctx, char* fpath)
{
    CGImageRef  imgRef = CGBitmapContextCreateImage(ctx);

    CFStringRef strRef = CFStringCreateWithCString(NULL, fpath, kCFStringEncodingMacRoman);
    CFStringRef typRef = CFStringCreateWithCString(NULL, "public.png", kCFStringEncodingMacRoman);
    CFURLRef    urlRef = CFURLCreateWithFileSystemPath(NULL, strRef, 0, false);

    CGImageDestinationRef dstRef = CGImageDestinationCreateWithURL(urlRef, typRef, 1, NULL);

    CGImageDestinationAddImage(dstRef, imgRef, NULL);
    CGImageDestinationFinalize(dstRef);

    CFRelease(dstRef);
    CFRelease(strRef);
    CFRelease(typRef);
    CFRelease(urlRef);
    CGImageRelease(imgRef);
}
char* loadImage(const char *fname)
{
	CGImageRef          imgRef = NULL;
	CGDataProviderRef   pdrSrc = NULL;
	CGImageSourceRef    imgSrc = NULL;

	pdrSrc = CGDataProviderCreateWithFilename(fname);
	imgSrc = CGImageSourceCreateWithDataProvider(pdrSrc, NULL);
	imgRef = CGImageSourceCreateImageAtIndex(imgSrc, 0, NULL);
	if(imgRef)
	{
		CFRelease(imgSrc);
		CFRelease(pdrSrc);
	}

    CGColorSpaceRef clrRef = NULL;
    CGContextRef    ctxRef = NULL;

	int W = CGImageGetWidth(imgRef);
	int H = CGImageGetHeight(imgRef);
	int S = CGImageGetBytesPerRow(imgRef);
    // RGBA
    CGBitmapInfo rgbInf = kCGImageAlphaPremultipliedLast;
    clrRef = CGColorSpaceCreateDeviceRGB();
    ctxRef = CGBitmapContextCreate(
    					NULL,
    					W,
    					H,
    					m_nColorDepth,
                        (m_nColorDepth == 16) ? S * 2 : S,
                        clrRef,
                        rgbInf
                       );
    CGColorSpaceRelease(clrRef);
    assert(ctxRef != NULL);

    CGContextDrawImage(ctxRef, CGRectMake(0, 0, W, H), imgRef);

    m_nWidth  = CGBitmapContextGetWidth(ctxRef);
    m_nHeight = CGBitmapContextGetHeight(ctxRef);
	m_nStride = CGBitmapContextGetBytesPerRow(ctxRef);
	m_nColorDepth = CGBitmapContextGetBitsPerComponent(ctxRef);

	CFRelease(imgRef);
	char* src = (char*) CGBitmapContextGetData(ctxRef);
	char* dst = new char[H * m_nStride];
	memcpy(dst, src, H * m_nStride);

	saveContext(ctxRef, (char*)"source.png");
	CGContextRelease(ctxRef);

	return dst;

}
void convRawDataToCGImage(char* src, int W, int H, int S, int D)
{
	int   len = H * S;

	CFDataRef         cfData = CFDataCreate(NULL, (UInt8*)src, len);
	CGDataProviderRef pdrRef = CGDataProviderCreateWithCFData(cfData);
	CGColorSpaceRef   clrRef = CGColorSpaceCreateDeviceRGB();
	// RGBA
	CGBitmapInfo 	  rgbInf = kCGImageAlphaPremultipliedLast;

	CGImageRef imgDst = CGImageCreate(
							W,
							H,
							D,
							m_nColorDepth*4,
							S,
							clrRef,
							rgbInf,
							pdrRef,
							NULL,
							false,
							kCGRenderingIntentDefault
							);
	CGColorSpaceRelease(clrRef);

	saveImage(imgDst, (char*)"target.png");

	CFRelease(imgDst);
	CFRelease(pdrRef);
	CFRelease(cfData);

}

void runOcl(char* rawData)
{
    unsigned int dataLen = m_nStride * m_nHeight;

    using namespace ocl;
    const char progStr[] = 
    "__kernel void addr(__global const char* src, __global const char* eff, __global char* dst)"
    "{"
    "    int idx = get_global_id(0);"
    "    dst[idx] = src[idx] + eff[idx];"
    "}";

    oclManager& mgr = oclManager::Instance();
    oclDevice&  dev = mgr.SelDevice(CL_DEVICE_TYPE_GPU);
    if (!dev.IsAvail())
    {
        fprintf(stderr, "[ERROR] Not a valid OpenCL Device!!!\n");
        return;
    }

    oclProgram  prg(dev, progStr);
    oclKernel&  krl = prg["addr"];

    char* src = rawData;
    char* dst = new char[dataLen];
    char* eff = new char[dataLen];
    for(unsigned int i = 0; i < dataLen; i++)
    {
        eff[i] = 127;
    }

    cl_mem Smem = oclBuf::R(src, dataLen);
    cl_mem Emem = oclBuf::R(eff, dataLen);
    cl_mem Dmem = oclBuf::W(dst, dataLen);

    size_t szGlobal[] = {dataLen};
    size_t szLocal[]  = {1};
    krl.SetDimensionAndSize(1, szGlobal, szLocal);
    krl.SetArg(0, &Smem);
    krl.SetArg(1, &Emem);
    krl.SetArg(2, &Dmem);
    krl.Run();
    oclBuf::read(Dmem, dst, dataLen);

    convRawDataToCGImage(dst, m_nWidth, m_nHeight, m_nStride, 8);
    
    clReleaseMemObject(Smem);
    clReleaseMemObject(Emem);
    clReleaseMemObject(Dmem);
    delete[] dst;
    
}
void runTest(char* imgfile)
{
    // 1) loadImage
    char* rawData = loadImage(imgfile);
	assert(rawData != NULL);

	runOcl(rawData);

	delete[] rawData;

}

int main(int argc, char** argv)
{
    if (argc < 2)
    {
        printf("Usage: ./a.out IMG_FILE_ABSOLUTE_PATH\n");
        printf("Ex: ./a.out Sun.jpg\n");
        printf("Ex: ./a.out Sun.jpg 0\n");
        printf("Ex: ./a.out Sun.jpg 1\n");
        printf("Ex: ./a.out Sun.jpg 1 0\n");
        return -1;
    }
	runTest(argv[1]);
    return 0;
}
